package pl.polsl.pum2.pj;

import java.nio.ByteBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import pl.polsl.pum2.pj.math.CustomTimer;
import pl.polsl.pum2.pj.math.QCamera;
import pl.polsl.pum2.pj.rubik.ColorAndId;
import pl.polsl.pum2.pj.rubik.Rubik;
import android.graphics.Point;
import android.opengl.GLSurfaceView.Renderer;
import android.util.DisplayMetrics;
import android.util.Log;

public class OpenGLRenderer implements Renderer {

  private Rubik               rubik;
  private CustomTimer         timer           = new CustomTimer();

  private QCamera             camera;

  private Point               viewport;

  private volatile Point      prevFinger      = null;
  private volatile Point      finger          = null;

  private volatile Point      dragStartPoint  = null;
  private volatile ColorAndId dragStartColor  = ColorAndId.zero;
  private volatile boolean    draggingAffects = false;

  /**
   * Constructor.
   * 
   * @param rubikSize
   *          varies from 2 (then cube is 2x2x2) to 7 (7x7x7)
   * @param shuffles
   *          number of shuffling tosses to perform
   * @param displayMetrics
   *          screen info
   */
  public OpenGLRenderer(int rubikSize, int shuffles, DisplayMetrics displayMetrics, float sensivity) {
    rubik = new Rubik(rubikSize, shuffles);
    camera = new QCamera(1.5f * rubikSize + 2, sensivity);
    viewport = new Point(displayMetrics.widthPixels, displayMetrics.heightPixels);
  }

  @Override
  public void onDrawFrame(GL10 gl) {
    camera.setView(gl);

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    rubik.draw(gl);
    gl.glLoadIdentity();

    long dt = timer.getIntervalMillis();
    if (rubik.update(dt)) {
      Log.i("CUBE", "SOLVED");
      RubikActivity.getInstance().cubeSolved();
      return;
    }

    processDragging(gl, dt);
  }

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {}

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

    camera.setView(gl);

    gl.glViewport(0, 0, viewport.x, viewport.y);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();

    // Calculate the aspect ratio of the window
    float ratio = (float) viewport.x / (float) viewport.y;
    gl.glFrustumf(-ratio, ratio, -1, 1, 1, 17);

    gl.glEnable(GL10.GL_DITHER);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
  }

  private ColorAndId readColorAndId(GL10 gl, Point point) {
    ByteBuffer buff = ByteBuffer.allocate(4);
    gl.glReadPixels(point.x, viewport.y - point.y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buff);
    buff.rewind();
        
    int r = normalize(buff.get());
    int g = normalize(buff.get());
    //htc fix
    int colorFix = 2;
    int halfColorVal = 128;
    if (g < halfColorVal + colorFix && g > halfColorVal - colorFix)
    	g = halfColorVal;
    //htc fix
    int b = normalize(buff.get());
    int a = normalize(buff.get());
    return new ColorAndId(r, g, b, a);
  }

  private int normalize(int a) {
    return a & 0xFF;
  }

  /**
   * Processes screen touch events.<br/>
   * Dragging background effects with rotating the camera around the cube.<br/>
   * Clicking on one of the cubes and dragging away rotates a single layer accordingly.
   * 
   * @param gl
   */
  private synchronized void processDragging(GL10 gl, long dt) {
    if (draggingAffects && dragStartPoint != null) {

      if (dragStartColor == null) {
        dragStartColor = readColorAndId(gl, dragStartPoint);
        Log.d("reading dragStartColor=", "" + dragStartColor);
      }

      if (ColorAndId.zero.equals(dragStartColor)) { // if dragging started from background
        if (prevFinger != null && !finger.equals(prevFinger)) {
          camera.rotate(finger.x - prevFinger.x, finger.y - prevFinger.y, dt);
        }
      } else { // dragging started from one of the cubes
        ColorAndId read = readColorAndId(gl, finger);
        if (!read.equals(dragStartColor)) {
          Log.d("rotating layer: from=" + dragStartColor, "; to=" + read);
          rubik.rotate(dragStartColor, read);
          dragStartPoint = null;
        }
      }

    }
    draggingAffects = false;
  }

  /**
   * <pre>
   * Dragging processing functions are synchronized. This aims to prevent inter-thread 
   * variables' modifications (caused by very frequent touch events calling startDrag(), 
   * drag() and endDrag()) during more complex calls: rendering with processDragging() 
   * which is being called separately.
   * </pre>
   */
  public synchronized void startDrag(Point initDrag) {
    Log.d("dragFrom=", initDrag.toString());
    draggingAffects = true;

    prevFinger = null;
    finger = initDrag;
    dragStartPoint = initDrag;
    dragStartColor = null;
  }

  public synchronized void endDrag(Point endDrag) {
    Log.d("dragTo=", endDrag.toString());
    draggingAffects = false;

    prevFinger = null;
    finger = null;
    dragStartPoint = null;
    dragStartColor = null;
  }

  public synchronized void drag(Point currentDrag) {
    Log.d("dragThru=", currentDrag.toString());
    draggingAffects = true;
    prevFinger = finger;
    finger = currentDrag;
  }
}
